// End-to-end integration tests for DVC (Data Version Control) operations
// This test simulates the exact DVC client behavior to ensure push/pull operations work correctly

use serde_json::Value;
use std::sync::Arc;
use std::time::Duration;
use tempfile::TempDir;
use tokio::time::timeout;

use axum::{
    extract::DefaultBodyLimit,
    http::{HeaderMap, Method},
    routing::{delete, get, post, put},
    Router,
};
use rdm_rust::config::AppConfig;
use rdm_rust::database::connection::establish_connection_pool;
use rdm_rust::handlers::{auth, data, dvc_remote};
use rdm_rust::services::{AuthService, DataService, DvcService, StorageService};
use rdm_rust::AppState;
use tower::ServiceBuilder;
use tower_http::{
    cors::{Any, CorsLayer},
    trace::TraceLayer,
};

// Test configuration
const TEST_SERVER_PORT: u16 = 0; // Use random available port
const TEST_TIMEOUT: Duration = Duration::from_secs(30);

struct DvcTestServer {
    port: u16,
    temp_dir: TempDir,
    base_url: String,
    client: reqwest::Client,
}

impl DvcTestServer {
    async fn new() -> Self {
        // Create temporary directory for test storage
        let temp_dir = TempDir::new().expect("Failed to create temp dir");

        // Load test configuration
        let mut config = AppConfig::from_env().expect("Failed to load config");
        config.server.port = TEST_SERVER_PORT;
        config.dvc.storage_path = temp_dir.path().to_string_lossy().to_string();

        // Initialize database
        let db_pool = establish_connection_pool(&config.database.url)
            .await
            .expect("Failed to establish database connection");

        // Initialize services
        let auth_service = Arc::new(AuthService::new(db_pool.clone(), config.clone()));
        let data_service = Arc::new(DataService::new(db_pool.clone()));
        let storage_service = Arc::new(StorageService::new(config.dvc.storage_path.clone()));
        let dvc_service = Arc::new(DvcService::new(db_pool.clone()));

        let app_state = AppState {
            auth_service,
            data_service,
            storage_service,
            dvc_service,
        };

        // Build application router (same as main.rs)
        let public_routes = Router::new()
            .route("/api/register", post(auth::register))
            .route("/api/register-admin", post(auth::register_admin))
            .route("/api/login", post(auth::login))
            .route("/api/data/public", get(data::list_public_data_items));

        let protected_routes = Router::new()
            .route("/api/user-info", get(auth::user_info))
            .route("/api/data", get(data::list_data_items))
            .route("/api/data/upload", post(data::upload_data))
            .route("/api/data/:id", get(data::get_data_item))
            .route("/api/data/:id", delete(data::delete_data_item))
            .route("/api/data/:id/download", get(data::download_data_file))
            .route("/api/data/:id/dvc", get(data::download_dvc_file))
            .route("/api/data/:id/dvc/content", get(data::get_dvc_content))
            .route("/api/data/:id/metadata", put(data::upload_metadata))
            .route("/api/dvc/files", get(dvc_remote::dvc_list_files))
            .route("/api/dvc/file/*path", get(dvc_remote::dvc_get_file_info))
            .route(
                "/api/dvc/remote/*path",
                axum::routing::any(dvc_remote::handle_dvc_request),
            )
            .route_layer(axum::middleware::from_fn_with_state(
                app_state.clone(),
                rdm_rust::middleware::auth::auth_middleware,
            ));

        let app = Router::new()
            .merge(public_routes)
            .merge(protected_routes)
            .layer(
                ServiceBuilder::new()
                    .layer(TraceLayer::new_for_http())
                    .layer(
                        CorsLayer::new()
                            .allow_origin(Any)
                            .allow_methods([
                                Method::GET,
                                Method::POST,
                                Method::PUT,
                                Method::DELETE,
                                Method::OPTIONS,
                                Method::HEAD,
                            ])
                            .allow_headers(Any),
                    )
                    .layer(DefaultBodyLimit::max(100 * 1024 * 1024)), // 100MB max file size
            )
            .with_state(app_state);

        // Start server on random port
        let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
            .await
            .expect("Failed to bind to address");
        let port = listener
            .local_addr()
            .expect("Failed to get local address")
            .port();

        // Spawn server task
        tokio::spawn(async move {
            axum::serve(listener, app)
                .await
                .expect("Failed to serve server");
        });

        // Give server time to start
        tokio::time::sleep(Duration::from_millis(100)).await;

        let base_url = format!("http://127.0.0.1:{}", port);
        let client = reqwest::Client::builder()
            .timeout(TEST_TIMEOUT)
            .build()
            .expect("Failed to create HTTP client");

        Self {
            port,
            temp_dir,
            base_url,
            client,
        }
    }

    async fn wait_for_server(&self) -> Result<(), Box<dyn std::error::Error>> {
        let health_url = format!("{}/api/data/public", self.base_url);

        for _ in 0..10 {
            match timeout(Duration::from_secs(1), self.client.get(&health_url).send()).await {
                Ok(Ok(response)) if response.status().is_success() => return Ok(()),
                Ok(_) => (),
                Err(_) => (),
            }
            tokio::time::sleep(Duration::from_millis(100)).await;
        }

        Err("Server failed to start".into())
    }
}

#[tokio::test]
async fn test_dvc_push_workflow_with_authentication() {
    let server = DvcTestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    println!("DVC Test server running on: {}", server.base_url);

    // Step 1: Register a test user
    let test_email = format!(
        "dvcuser{}@hillstonenet.com",
        chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)
    );
    let test_password = "dvcpassword123";

    let register_response = server
        .client
        .post(format!("{}/api/register", server.base_url))
        .json(&serde_json::json!({
            "email": test_email,
            "password": test_password
        }))
        .send()
        .await
        .expect("Failed to send register request");

    assert_eq!(
        register_response.status(),
        201,
        "User registration should succeed"
    );

    // Step 2: Login to get auth token
    let login_response = server
        .client
        .post(format!("{}/api/login", server.base_url))
        .json(&serde_json::json!({
            "email": test_email,
            "password": test_password
        }))
        .send()
        .await
        .expect("Failed to send login request");

    assert_eq!(login_response.status(), 200, "Login should succeed");

    let login_body: Value = login_response
        .json()
        .await
        .expect("Failed to parse login response");
    let auth_token = login_body
        .get("access_token")
        .or_else(|| login_body.get("token"))
        .and_then(|t| t.as_str())
        .expect("Login response should contain token");

    println!("Successfully authenticated user: {}", test_email);

    // Step 3: Simulate DVC push operation
    // DVC sends PUT requests to /api/dvc/remote/{file_path}
    let test_file_content =
        b"This is a test file for DVC push.\nIt contains sample data.\nEnd of test.";
    let test_file_path = "data/test_dataset.csv";

    // Create DVC PUT request with Basic Auth (as DVC client would do)
    let dvc_put_response = server
        .client
        .put(format!(
            "{}/api/dvc/remote/{}",
            server.base_url, test_file_path
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .header("Content-Type", "application/octet-stream")
        .body(test_file_content.to_vec())
        .send()
        .await
        .expect("Failed to send DVC PUT request");

    assert_eq!(dvc_put_response.status(), 200, "DVC PUT should succeed");

    println!("Successfully pushed file via DVC: {}", test_file_path);

    // Step 4: Verify file appears in DVC files list
    let dvc_list_response = server
        .client
        .get(format!("{}/api/dvc/files", server.base_url))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send DVC list request");

    assert_eq!(dvc_list_response.status(), 200, "DVC list should succeed");

    let dvc_list_body: Value = dvc_list_response
        .json()
        .await
        .expect("Failed to parse DVC list response");
    println!(
        "DVC list response: {}",
        serde_json::to_string_pretty(&dvc_list_body).unwrap()
    );

    let empty_vec = vec![];
    let files = dvc_list_body.as_array().unwrap_or(&empty_vec);

    assert!(
        !files.is_empty(),
        "DVC files list should contain pushed file. Got: {:?}",
        dvc_list_body
    );

    let pushed_file = files
        .iter()
        .find(|file| file.get("path").unwrap().as_str().unwrap() == test_file_path)
        .expect("Pushed file should be in DVC list");

    assert_eq!(pushed_file.get("exists").unwrap().as_bool().unwrap(), true);
    assert!(pushed_file.get("size").unwrap().as_i64().unwrap() > 0);

    println!("Verified file appears in DVC list");

    // Step 5: Test DVC HEAD request (file existence check)
    let dvc_head_response = server
        .client
        .head(format!(
            "{}/api/dvc/remote/{}",
            server.base_url, test_file_path
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send DVC HEAD request");

    assert_eq!(dvc_head_response.status(), 200, "DVC HEAD should succeed");

    let head_headers = dvc_head_response.headers();
    assert!(head_headers.contains_key("content-length"));
    assert!(head_headers.contains_key("etag"));

    println!("Verified DVC HEAD request works");

    // Step 6: Test DVC GET request (file download)
    let dvc_get_response = server
        .client
        .get(format!(
            "{}/api/dvc/remote/{}",
            server.base_url, test_file_path
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send DVC GET request");

    assert_eq!(dvc_get_response.status(), 200, "DVC GET should succeed");

    let downloaded_content = dvc_get_response
        .bytes()
        .await
        .expect("Failed to read downloaded content");
    assert_eq!(
        downloaded_content.as_ref(),
        test_file_content,
        "Downloaded content should match original"
    );

    println!("Verified DVC GET returns correct content");

    // Step 7: Test DVC file info endpoint
    let dvc_info_response = server
        .client
        .get(format!(
            "{}/api/dvc/file/{}",
            server.base_url, test_file_path
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send DVC info request");

    assert_eq!(dvc_info_response.status(), 200, "DVC info should succeed");

    let dvc_info_body: Value = dvc_info_response
        .json()
        .await
        .expect("Failed to parse DVC info response");
    assert_eq!(
        dvc_info_body.get("path").unwrap().as_str().unwrap(),
        test_file_path
    );
    assert_eq!(
        dvc_info_body.get("exists").unwrap().as_bool().unwrap(),
        true
    );

    println!("Verified DVC file info endpoint");

    // Step 8: Test DVC DELETE request
    let dvc_delete_response = server
        .client
        .delete(format!(
            "{}/api/dvc/remote/{}",
            server.base_url, test_file_path
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send DVC DELETE request");

    assert_eq!(
        dvc_delete_response.status(),
        204,
        "DVC DELETE should succeed"
    );

    println!("Successfully deleted file via DVC");

    // Step 9: Verify file is gone
    let dvc_head_after_delete = server
        .client
        .head(format!(
            "{}/api/dvc/remote/{}",
            server.base_url, test_file_path
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send DVC HEAD after delete");

    assert_eq!(
        dvc_head_after_delete.status(),
        404,
        "DVC HEAD should return 404 after delete"
    );

    println!("Verified file was successfully deleted");
}

#[tokio::test]
async fn test_dvc_push_without_authentication() {
    let server = DvcTestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    let test_file_content = b"This should fail without auth";
    let test_file_path = "data/unauthorized.csv";

    // Try DVC PUT without authentication
    let dvc_put_response = server
        .client
        .put(format!(
            "{}/api/dvc/remote/{}",
            server.base_url, test_file_path
        ))
        .header("Content-Type", "application/octet-stream")
        .body(test_file_content.to_vec())
        .send()
        .await
        .expect("Failed to send DVC PUT request");

    assert_eq!(
        dvc_put_response.status(),
        401,
        "DVC PUT without auth should fail"
    );

    println!("Correctly rejected unauthorized DVC PUT request");
}

#[tokio::test]
async fn test_dvc_multiple_files_workflow() {
    let server = DvcTestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    // Register and login
    let test_email = "multidvc@hillstonenet.com";
    let test_password = "password123";

    server
        .client
        .post(format!("{}/api/register", server.base_url))
        .json(&serde_json::json!({
            "email": test_email,
            "password": test_password
        }))
        .send()
        .await
        .expect("Failed to register");

    let login_response = server
        .client
        .post(format!("{}/api/login", server.base_url))
        .json(&serde_json::json!({
            "email": test_email,
            "password": test_password
        }))
        .send()
        .await
        .expect("Failed to login");

    let login_body: Value = login_response.json().await.expect("Failed to parse login");
    let auth_token = login_body
        .get("access_token")
        .or_else(|| login_body.get("token"))
        .and_then(|t| t.as_str())
        .expect("Login response should contain token");

    // Push multiple files via DVC
    let files = vec![
        ("data/dataset1.csv", "Dataset 1 content".as_bytes()),
        ("data/dataset2.csv", "Dataset 2 content".as_bytes()),
        ("models/model1.pkl", "Model 1 binary data".as_bytes()),
        (
            "results/output.json",
            "{\"result\": \"success\"}".as_bytes(),
        ),
    ];

    for (file_path, content) in &files {
        let dvc_put_response = server
            .client
            .put(format!("{}/api/dvc/remote/{}", server.base_url, file_path))
            .header("Authorization", format!("Bearer {}", auth_token))
            .header("Content-Type", "application/octet-stream")
            .body(content.to_vec())
            .send()
            .await
            .expect("Failed to send DVC PUT request");

        assert_eq!(
            dvc_put_response.status(),
            200,
            "DVC PUT should succeed for {}",
            file_path
        );
    }

    // Verify all files appear in list
    let dvc_list_response = server
        .client
        .get(format!("{}/api/dvc/files", server.base_url))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send DVC list request");

    assert_eq!(dvc_list_response.status(), 200);

    let dvc_list_body: Value = dvc_list_response
        .json()
        .await
        .expect("Failed to parse DVC list response");
    let files_list = dvc_list_body
        .as_array()
        .expect("DVC list should return an array");
    assert_eq!(
        files_list.len(),
        files.len(),
        "All pushed files should be in the list"
    );

    println!(
        "Successfully pushed and verified {} files via DVC",
        files.len()
    );
}

#[tokio::test]
async fn test_dvc_large_file_push() {
    let server = DvcTestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    // Register and login
    let test_email = "largedvc@hillstonenet.com";
    let test_password = "password123";

    server
        .client
        .post(format!("{}/api/register", server.base_url))
        .json(&serde_json::json!({
            "email": test_email,
            "password": test_password
        }))
        .send()
        .await
        .expect("Failed to register");

    let login_response = server
        .client
        .post(format!("{}/api/login", server.base_url))
        .json(&serde_json::json!({
            "email": test_email,
            "password": test_password
        }))
        .send()
        .await
        .expect("Failed to login");

    let login_body: Value = login_response.json().await.expect("Failed to parse login");
    let auth_token = login_body
        .get("access_token")
        .or_else(|| login_body.get("token"))
        .and_then(|t| t.as_str())
        .expect("Login response should contain token");

    // Create a 10MB file (large but within limits)
    let large_content = vec![0u8; 10 * 1024 * 1024];
    let test_file_path = "data/large_dataset.bin";

    let dvc_put_response = server
        .client
        .put(format!(
            "{}/api/dvc/remote/{}",
            server.base_url, test_file_path
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .header("Content-Type", "application/octet-stream")
        .body(large_content.clone())
        .send()
        .await
        .expect("Failed to send DVC PUT request");

    assert_eq!(
        dvc_put_response.status(),
        200,
        "DVC PUT of large file should succeed"
    );

    // Verify the file size
    let dvc_info_response = server
        .client
        .get(format!(
            "{}/api/dvc/file/{}",
            server.base_url, test_file_path
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send DVC info request");

    assert_eq!(dvc_info_response.status(), 200);

    let dvc_info_body: Value = dvc_info_response
        .json()
        .await
        .expect("Failed to parse DVC info response");
    assert_eq!(
        dvc_info_body.get("size").unwrap().as_i64().unwrap(),
        large_content.len() as i64
    );

    println!("Successfully pushed 10MB file via DVC");
}
